/**
 * @brief 参考资料 https://www.cnblogs.com/WindSun/p/12142656.html
 */

#pragma once
#include <event.h>
#include <event2/thread.h>
#include <event2/util.h>
#include <map>
#include <set>
#include <signal.h>

#include "../DataType/BiMap.hpp"
#include "../DataType/ByteArray.hpp"
#include "../GLogHelper.hpp"
#include "../Thread/ThreadManager.hpp"
#include "../Timer/Timer.hpp"

namespace MyUtils
{
namespace Net
{
using MyUtils::DataType::ByteArray;
typedef std::function< void( ByteArray ) > fd_read_cb;

// TODO 线程安全
class FdManager
{
  private:
    struct event_base *m_base;
    struct event *m_signal_event;
    MyUtils::DataType::BiMap< int, bufferevent * > m_fd2bev;
    std::map< int, fd_read_cb > m_fd2wrapper; //句柄到 FdWrapper 的映射
    MyUtils::DataType::BiMap< int, std::string > m_fd2name; //句柄到命名的映射

  public:
    /**
     * @brief
     * @param [in] rto 超时重传时间，以毫秒为单位
     */
    explicit FdManager( )
        : m_base( NULL ), m_signal_event( NULL ), m_fd2bev( ), m_fd2wrapper( ),
          m_fd2name( )
    {
        evthread_use_pthreads( ); //会自动添加锁机制
        m_base = event_base_new( );
        if ( m_base != NULL )
        {
            m_signal_event =
                evsignal_new( m_base, SIGINT, __signal_cb, ( void * ) this );
        }
    }
    ~FdManager( ) { __signal_cb( SIGINT, 0, this ); }

    /**
     * @brief 启动监听程序。该函数应当放在一个单独的线程中阻塞执行
     */
    inline void run( ) { event_base_loop( m_base, EVLOOP_NO_EXIT_ON_EMPTY ); }

    /**
     * @brief 添加待监听的fd.
     * @param [in] name 连接名字
     * @param [in] fd 文件描述符
     * @param [in] f_readcb read回调函数，可以参考本文件中的样例
     * @param [in] writecb write回调函数，一般置为NULL
     * @param [in] eventcb 异常处理回调函数，传入参数为NULL时将使用默认处理函数
     * @param [in] cbarg 前三个回调函数的传入参数
     * @throw 当出现重复添加时会抛出异常
     * @return bufferevent *
     */
    void add( const std::string &name, int fd, fd_read_cb f_readcb,
              bufferevent_data_cb writecb = NULL,
              bufferevent_event_cb eventcb = NULL, void *cbarg = NULL )
    {
        // TODO 修改传入参数
        //检查fd是否重复插入
        auto it = this->m_fd2name.find_by_first( fd );
        if ( it != this->m_fd2name.end( ) )
        {
            char temp[ 1024 ];
            sprintf( temp, "%d 已经被添加进来了\n", fd );
            throw( temp );
            return;
        }
        //检查是否连接重名
        it = this->m_fd2name.find_by_second( name );
        if ( it != this->m_fd2name.end( ) )
        {
            throw( name + "连接命名重复" );
            return;
        }

        this->m_fd2name.insert( std::make_pair( fd, name ) );

        bufferevent *bev = bufferevent_socket_new(
            m_base, fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE );

        bufferevent_setcb( bev, __read_cb, NULL, __event_cb, ( void * ) this );

        bufferevent_enable( bev, EV_READ | EV_PERSIST );

        m_fd2bev.insert( std::make_pair( fd, bev ) );

        this->m_fd2wrapper.insert( std::make_pair( fd, f_readcb ) );
        return;
    }

    size_t size( ) const { return this->m_fd2name.size( ); }

    /**
     * @brief 查找是否存在名为name的连接
     * @param [in] name 连接名
     * @return true 表示存在，否则不存在
     */
    bool exist( const std::string &name )
    {
        return ( !( this->m_fd2name.find_by_second( name ) ==
                    this->m_fd2name.end( ) ) );
    }

    /**
     * @brief 移除对应名称的连接
     * @param [in] name 连接名称
     */
    void remove( const std::string &name )
    {

        auto it = this->m_fd2name.find_by_second( name );
        if ( it != this->m_fd2name.end( ) )
        {
            __remove( it->first );
        }
        else
        {
            std::string err( "名为\"" + name + "\"的连接不存在" );
            throw( err );
        }
        return;
    }

  private:
    void __remove( int fd )
    {
        this->m_fd2name.erase_by_first( fd );
        this->m_fd2bev.erase_by_first( fd );
        this->m_fd2wrapper.erase( fd );
        return;
    }

  public:
    /**
     * @brief 通过名为name的连接向外发送消息
     * @param [in] name 连接名
     * @param [in] data 需要发送的数据
     * @param [in] data_len 数据长度
     * @return 0 if successful, -2 the name is error, -1 error occurred when
     */
    int send( const std::string &name, const char *data,
              const size_t &data_len ) noexcept
    {
        auto it = this->m_fd2name.find_by_second( name );
        if ( it != this->m_fd2name.end( ) )
        {
            auto it2 = this->m_fd2bev.find_by_first( it->first );
            return bufferevent_write( it2->second, data, data_len );
        }
        return -2;
    }

  private:
    static void __event_cb( struct bufferevent *bev, short what, void *arg )
    {
        if ( what & BEV_EVENT_EOF )
        {
            MYLOG_INFO( "BEV_EVENT_EOF\n" );
        }
        else if ( what & BEV_EVENT_ERROR )
        {
            MYLOG_ERROR( "BEV_EVENT_ERROR\n" );
        }
        else if ( what & BEV_EVENT_CONNECTED )
        {
            MYLOG_INFO( "BEV_EVENT_CONNECTED\n" );
            return;
        }
        //这将自动close套接字和free读写缓冲区
        FdManager *ths = ( FdManager * ) arg;
        ths->__remove( ths->m_fd2bev.find_by_second( bev )->first );
    }
    static void __signal_cb( evutil_socket_t sig, short events,
                             void *user_data )
    {
        MYLOG_ERROR( "Caught an interrupt signal; exiting cleanly in two "
                     "seconds.\n" );

        FdManager *ths = ( FdManager * ) user_data;

        for ( auto it = ths->m_fd2name.begin( ); it != ths->m_fd2name.end( );
              ++it )
        {
            ths->remove( it->second );
        }

        event_free( ths->m_signal_event );
        event_base_loopbreak( ths->m_base );
        event_base_free( ths->m_base );
    }

    static void __read_cb( struct bufferevent *bev, void *arg )
    {
        char msg
            [ 4096 ]; // TODO
                      // 这里限定死了每次最多读取的字节数目不合适，应当由使用者指定
        size_t len = bufferevent_read( bev, msg, sizeof( msg ) );

        FdManager *ths = ( FdManager * ) arg;
        auto it = ths->m_fd2wrapper.find(
            ths->m_fd2bev.find_by_second( bev )->first );
        ByteArray ba( msg, len );
        it->second( ba );
    }
};

} // namespace Net

} // namespace MyUtils
